package com.general.framework.core.utils;

import com.general.framework.core.exception.Ex;
import com.general.framework.core.lang.Jsons;
import com.general.framework.core.lang.Strings;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 微信API请求
 * @author leejean
 * @date 2020年05月25 17:42:33
 */
@Slf4j
public class WxApiRequest {

    /**
     * 获取accessToken
     * @param appId appId
     * @param appSecret app秘钥
     * @return accessToken
     */
    public static String getAccessToken(final String appId, final String appSecret) {
        final String requestUrl = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
        Map<String, Object> resultMap = WxApiRequest.requestWxApi(requestUrl, "access_token");
        return Strings.defaultString(resultMap.get("access_token"));
    }

    /**
     * 获取ticket
     * @param accessToken accessToken
     * @return ticket
     */
    public static String getTicket(final String accessToken) {
        final String requestUrl = String.format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", accessToken);
        Map<String, Object> resultMap = WxApiRequest.requestWxApi(requestUrl, "ticket");
        return Strings.defaultString(resultMap.get("ticket"));
    }

    /**
     * 获取手机号
     * @param accessToken accessToken
     * @return ticket
     */
    public static String getPhoneNumber(final String accessToken, final String code) {
        final String requestUrl = String.format("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s", accessToken);
        Map<String, Object> param = new HashMap<>();
        param.put("code", code);
        Map<String, Object> resultMap = WxApiRequest.requestWxPostApi(requestUrl, param, "获取微信手机号");
        return Strings.defaultString(resultMap.get("phone_info"));
    }

    /**
     * 获取网页授权的accessToken
     * @param appId appId
     * @param appSecret app秘钥
     * @return accessToken
     */
    public static Map<String, Object> getAuthAccessToken(final String appId, final String appSecret, final String code) {
        final String requestUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appId, appSecret, code);
        return WxApiRequest.requestWxApi(requestUrl, "access_token");
    }

    /**
     * 公众号授权获取用户信息
     * @param appId appId
     * @param appSecret app秘钥
     * @param code code
     * @return 用户信息
     */
    public static Map<String, Object> getUserInfoByCode(final String appId, final String appSecret, final String code) {
        final Map<String, Object> authMap = getAuthAccessToken(appId, appSecret, code);
        final String accessToken = Strings.defaultString(authMap.get("access_token"));
        final String openId = Strings.defaultString(authMap.get("openid"));
        final String userRequestUrl = String.format("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", accessToken, openId);
        return  WxApiRequest.requestWxApi(userRequestUrl, "openid");
    }

    /**
     * 公众号静默获取用户OpenId
     */
    public static String getOpenIdByCode(final String appId, final String appSecret, final String code) {
        final Map<String, Object> authMap = getAuthAccessToken(appId, appSecret, code);
        return Strings.defaultString(authMap.get("openid"));
    }

    /**
     * 获取用户session信息
     * @param appId appId
     * @param appSecret app秘钥
     * @param code code
     * @return 用户session信息
     */
    public static Map<String, Object> getWxSession(final String appId, final String appSecret, final String code) {
        final String requestUrl = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appId, appSecret, code);
        final Map<String, Object> resultMap = WxApiRequest.requestWxApi(requestUrl, "session_key", "openid");
        if (resultMap.get("session_key") != null && resultMap.get("session_key") != null) {
            return resultMap;
        } else {
            log.error("获取微信session失败，requestUrl={}, response={}", requestUrl, Jsons.toJsonString(resultMap));
            throw Ex.business("微信请求异常");
        }
    }

    /**
     * 解析微信加密数据
     * @param sessionKey sessionKey
     * @param encryptedData encryptedData
     * @param iv iv
     * @return 解密数据
     */
    public static String decryptWxData(String sessionKey, String encryptedData, String iv) {
        byte[] resultByte = null;
        try {
            resultByte = AESUtil.decrypt(Base64.getDecoder().decode(encryptedData.getBytes()),
                    Base64.getDecoder().decode(sessionKey.getBytes()),
                    Base64.getDecoder().decode(iv.getBytes()));
        } catch (Exception e) {
            log.error("解析微信Data数据失败", e);
        }
        if (resultByte == null || resultByte.length == 0) {
            log.error("解析微信加密数据失败，响应数据为空，sessionKey={}, encryptedData={}, iv={}", sessionKey, encryptedData, iv);
            throw Ex.business("微信请求异常");
        }
        return new String(resultByte, Charset.forName("utf-8"));
    }

    /**
     * 验证微信token
     * @param accessToken accessToken
     * @param openId openId
     */
    public static void verifyAccessToken(String accessToken, String openId) {
        final String requestUrl = String.format("https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s", accessToken, openId);
        final Map<String, Object> resultMap = WxApiRequest.requestWxApi(requestUrl);
        final String errorCode = Strings.defaultString(resultMap.get("errcode"));
        if (errorCode != null && !Strings.equals("0", errorCode)) {
            log.error("微信登录异常，检验accessToken有效性失败，request={}, response={}", requestUrl, Jsons.toJsonString(resultMap));
            throw Ex.business("微信请求异常");
        }
    }

    /**
     * 请求并验证微信API接口响应数据
     * @param requestUrl requestUrl
     * @param verifyKeys verifyKeys
     * @return 响应数据
     */
    private static Map<String, Object> requestWxApi(final String requestUrl, final String... verifyKeys) {
        final String response = HttpClientUtil.httpGet(requestUrl);
        return validateWxApiResponse(requestUrl, response, verifyKeys);
    }

    public static Map<String, Object> requestWxPostApi(final String url, final Map<String, Object> params, final String desc) {
        final String paramStr = Jsons.toJsonString(params);
        final String responseResult;
        try {
            Map<String, String> header = new HashMap<>(1);
            header.put("Content-Type", "application/json");
            responseResult = HttpClientUtil.httpPost(url, header, paramStr);
        } catch (Exception e) {
            log.error("{}异常，params={}", desc, paramStr, e);
            throw Ex.systemError();
        }
        return getWxApiRequestResult(responseResult);
    }

    /**
     * 获取微信小程序响应数据
     * @param response
     * @return
     */
    public static Map<String, Object> getWxApiRequestResult(final String response) {
        if (Strings.isBlank(response)) {
            log.error("请求获取微信接口返回数据异常，响应数据为空");
            throw Ex.systemError("微信服务器异常");
        }
        final Map<String, Object> resultMap = Jsons.parseObject(response, Map.class);
        final String errorCode = Strings.defaultString(resultMap.get("errcode"));
        if (Strings.isNotBlank(errorCode) && !Strings.equals(errorCode, "0")) {
            log.error("请求获取微信接口返回errCode异常，response={}", response);
            throw Ex.systemError("请求微信接口返回数据异常");
        }
        return resultMap;
    }

    private static Map<String, Object> validateWxApiResponse(String requestUrl, String response, String[] verifyKeys) {
        if (Strings.isBlank(response)) {
            log.error("微信请求异常，响应结果为空，requestUrl={}", requestUrl);
            throw Ex.business("微信请求异常");
        }
        final Map<String, Object> resultMap = Jsons.parseObject(response, Map.class);
        // 验证响应数据
        if (verifyKeys != null && verifyKeys.length > 0) {
            for (String verifyKey : verifyKeys) {
                if (!resultMap.containsKey(verifyKey)) {
                    log.error("微信请求异常，响应数据不包含{}值, response={}", verifyKey, response);
                    throw Ex.business("微信请求异常");
                }
            }
        }
        return resultMap;
    }



    public static byte[] getLiteQRCode(final String liteAccessToken, final String pageUrl, final String param){
        String url = String.format( "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", liteAccessToken);
        Map<String,String> map = new HashMap<>();
        map.put("scene", param);
        map.put("page", pageUrl);
        return HttpClientUtil.httpPostToByte(url, Jsons.toJsonString(map));
    }

    /**
     * 检测小程序token是否有效
     * @param accessToken accessToken
     * @return 是否有效
     */
    public static boolean checkLiteAccessToken(String accessToken){
        final String requestUrl = String.format("https://api.weixin.qq.com/wxaapi/feedback/list?access_token=%s&page=1&num=1", accessToken);
        String respStr = HttpClientUtil.httpGet(requestUrl);
        log.info("检测小程序accessToken返回{}",respStr);
        Map<String,Object> respMap = Jsons.parseObject(respStr, Map.class);
        if(respMap.containsKey("errcode") && Objects.equals(Strings.defaultString(respMap.get("errcode")) , "40001")){
            log.info("小程序token{}已失效#############",accessToken);
            return false;
        }
        return true;
    }

    /**
     * 推送微信公众号消息
     * @param accessToken accessToken
     * @param requestParam requestParam
     */
    public static void pushOfficialMessage(final String accessToken, Map<String, Object> requestParam) {
        log.info("pushOfficialMessage===>accessToken:{}, requestParam:{}", accessToken, Jsons.toJsonString(requestParam));
        final String url = String.format("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s", accessToken);
        final Map<String, Object> response = WxApiRequest.requestWxPostApi(url, requestParam, "推送微信公众号消息");
        log.info("pushOfficialMessage<==={}", Jsons.toJsonString(response));
    }

    /**
     * 推送微信小程序订阅消息
     * @param accessToken accessToken
     * @param requestParam requestParam
     */
    public static void pushSubscribeMessage(final String accessToken, Map<String, Object> requestParam) {
        log.info("pushSubscribeMessage===>accessToken:{}, requestParam:{}", accessToken, Jsons.toJsonString(requestParam));
        final String url = String.format("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s", accessToken);
        final Map<String, Object> response = WxApiRequest.requestWxPostApi(url, requestParam, "推送微信小程序订阅消息");
        log.info("pushSubscribeMessage<==={}", Jsons.toJsonString(response));
    }

    /**
     * 推送服务消息
     * @param accessToken accessToken
     * @param requestParam requestParam
     */
    public static void pushServiceMessage(final String accessToken, Map<String, Object> requestParam) {
        log.info("pushServiceMessage===>accessToken:{}, requestParam:{}", accessToken, Jsons.toJsonString(requestParam));
        final String url = String.format("https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=%s", accessToken);
        final Map<String, Object> response = WxApiRequest.requestWxPostApi(url, requestParam, "推送服务消息");
        log.info("pushServiceMessage<==={}", Jsons.toJsonString(response));
    }

    /**
     * 获取二维码
     * @param accessToken accessToken
     * @param requestParam requestParam
     */
    public static byte[] getUnlimitedQRCode(final String accessToken, Map<String, Object> requestParam) {
        log.info("getUnlimitedQRCode===>accessToken:{}, requestParam:{}", accessToken, Jsons.toJsonString(requestParam));
        final String url = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken);
        return HttpClientUtil.httpPostToByte(url, Jsons.toJsonString(requestParam));
    }

    public static String bytes2Base64(byte[] bytes){
        byte[] base64bytes = Base64.getEncoder().encode(bytes);
        return  "data:image/jpeg;base64," + new String(base64bytes);
    }

    /**
     * 生成微信小程序 URL Scheme
     * @param accessToken accessToken
     * @param requestParam requestParam
     * @return scheme响应结果
     */
    public static Map<String, Object> generateScheme(final String accessToken, Map<String, Object> requestParam) {
        log.info("generateScheme===>accessToken:{}, requestParam:{}", accessToken, Jsons.toJsonString(requestParam));
        final String url = String.format("https://api.weixin.qq.com/wxa/generatescheme?access_token=%s", accessToken);
        final Map<String, Object> response = WxApiRequest.requestWxPostApi(url, requestParam, "生成微信小程序URL Scheme");
        log.info("generateScheme<==={}", Jsons.toJsonString(response));
        return response;
    }

}
